package com.baomidou.kisso.common.bcprov.math.ec;

import java.math.BigInteger;

/**
 * Class representing a simple version of a big decimal. A
 * <code>SimpleBigDecimal</code> is basically a
 * {@link java.math.BigInteger BigInteger} with a few digits on the right of
 * the decimal point. The number of (binary) digits on the right of the decimal
 * point is called the <code>scale</code> of the <code>SimpleBigDecimal</code>.
 * Unlike in {@link java.math.BigDecimal BigDecimal}, the scale is not adjusted
 * automatically, but must be set manually. All <code>SimpleBigDecimal</code>s
 * taking part in the same arithmetic operation must have equal scale. The
 * result of a multiplication of two <code>SimpleBigDecimal</code>s returns a
 * <code>SimpleBigDecimal</code> with double scale.
 */
class SimpleBigDecimal
//extends Number   // not in J2ME - add compatibility class?
{

	private static final long serialVersionUID = 1L;

	private final BigInteger bigInt;

	private final int scale;


	/**
	 * Returns a <code>SimpleBigDecimal</code> representing the same numerical
	 * value as <code>value</code>.
	 * @param value The value of the <code>SimpleBigDecimal</code> to be
	 * created. 
	 * @param scale The scale of the <code>SimpleBigDecimal</code> to be
	 * created. 
	 * @return The such created <code>SimpleBigDecimal</code>.
	 */
	public static SimpleBigDecimal getInstance( BigInteger value, int scale ) {
		return new SimpleBigDecimal(value.shiftLeft(scale), scale);
	}


	/**
	 * Constructor for <code>SimpleBigDecimal</code>. The value of the
	 * constructed <code>SimpleBigDecimal</code> equals <code>bigInt / 
	 * 2<sup>scale</sup></code>.
	 * @param bigInt The <code>bigInt</code> value parameter.
	 * @param scale The scale of the constructed <code>SimpleBigDecimal</code>.
	 */
	public SimpleBigDecimal( BigInteger bigInt, int scale ) {
		if ( scale < 0 ) {
			throw new IllegalArgumentException("scale may not be negative");
		}

		this.bigInt = bigInt;
		this.scale = scale;
	}


	private SimpleBigDecimal( SimpleBigDecimal limBigDec ) {
		bigInt = limBigDec.bigInt;
		scale = limBigDec.scale;
	}


	private void checkScale( SimpleBigDecimal b ) {
		if ( scale != b.scale ) {
			throw new IllegalArgumentException(
					"Only SimpleBigDecimal of " + "same scale allowed in arithmetic operations");
		}
	}


	public SimpleBigDecimal adjustScale( int newScale ) {
		if ( newScale < 0 ) {
			throw new IllegalArgumentException("scale may not be negative");
		}

		if ( newScale == scale ) {
			return new SimpleBigDecimal(this);
		}

		return new SimpleBigDecimal(bigInt.shiftLeft(newScale - scale), newScale);
	}


	public SimpleBigDecimal add( SimpleBigDecimal b ) {
		checkScale(b);
		return new SimpleBigDecimal(bigInt.add(b.bigInt), scale);
	}


	public SimpleBigDecimal add( BigInteger b ) {
		return new SimpleBigDecimal(bigInt.add(b.shiftLeft(scale)), scale);
	}


	public SimpleBigDecimal negate() {
		return new SimpleBigDecimal(bigInt.negate(), scale);
	}


	public SimpleBigDecimal subtract( SimpleBigDecimal b ) {
		return add(b.negate());
	}


	public SimpleBigDecimal subtract( BigInteger b ) {
		return new SimpleBigDecimal(bigInt.subtract(b.shiftLeft(scale)), scale);
	}


	public SimpleBigDecimal multiply( SimpleBigDecimal b ) {
		checkScale(b);
		return new SimpleBigDecimal(bigInt.multiply(b.bigInt), scale + scale);
	}


	public SimpleBigDecimal multiply( BigInteger b ) {
		return new SimpleBigDecimal(bigInt.multiply(b), scale);
	}


	public SimpleBigDecimal divide( SimpleBigDecimal b ) {
		checkScale(b);
		BigInteger dividend = bigInt.shiftLeft(scale);
		return new SimpleBigDecimal(dividend.divide(b.bigInt), scale);
	}


	public SimpleBigDecimal divide( BigInteger b ) {
		return new SimpleBigDecimal(bigInt.divide(b), scale);
	}


	public SimpleBigDecimal shiftLeft( int n ) {
		return new SimpleBigDecimal(bigInt.shiftLeft(n), scale);
	}


	public int compareTo( SimpleBigDecimal val ) {
		checkScale(val);
		return bigInt.compareTo(val.bigInt);
	}


	public int compareTo( BigInteger val ) {
		return bigInt.compareTo(val.shiftLeft(scale));
	}


	public BigInteger floor() {
		return bigInt.shiftRight(scale);
	}


	public BigInteger round() {
		SimpleBigDecimal oneHalf = new SimpleBigDecimal(ECConstants.ONE, 1);
		return add(oneHalf.adjustScale(scale)).floor();
	}


	public int intValue() {
		return floor().intValue();
	}


	public long longValue() {
		return floor().longValue();
	}


	/* NON-J2ME compliant.
	public double doubleValue()
	{
	return Double.valueOf(toString()).doubleValue();
	}
	
	public float floatValue()
	{
	return Float.valueOf(toString()).floatValue();
	}
	*/
	public int getScale() {
		return scale;
	}


	public String toString() {
		if ( scale == 0 ) {
			return bigInt.toString();
		}

		BigInteger floorBigInt = floor();

		BigInteger fract = bigInt.subtract(floorBigInt.shiftLeft(scale));
		if ( bigInt.signum() == -1 ) {
			fract = ECConstants.ONE.shiftLeft(scale).subtract(fract);
		}

		if ( (floorBigInt.signum() == -1) && (!(fract.equals(ECConstants.ZERO))) ) {
			floorBigInt = floorBigInt.add(ECConstants.ONE);
		}
		String leftOfPoint = floorBigInt.toString();

		char[] fractCharArr = new char[scale];
		String fractStr = fract.toString(2);
		int fractLen = fractStr.length();
		int zeroes = scale - fractLen;
		for ( int i = 0 ; i < zeroes ; i++ ) {
			fractCharArr[i] = '0';
		}
		for ( int j = 0 ; j < fractLen ; j++ ) {
			fractCharArr[zeroes + j] = fractStr.charAt(j);
		}
		String rightOfPoint = new String(fractCharArr);

		StringBuffer sb = new StringBuffer(leftOfPoint);
		sb.append(".");
		sb.append(rightOfPoint);

		return sb.toString();
	}


	public boolean equals( Object o ) {
		if ( this == o ) {
			return true;
		}

		if ( !(o instanceof SimpleBigDecimal) ) {
			return false;
		}

		SimpleBigDecimal other = (SimpleBigDecimal) o;
		return ((bigInt.equals(other.bigInt)) && (scale == other.scale));
	}


	public int hashCode() {
		return bigInt.hashCode() ^ scale;
	}

}
